#include "Epoller.hpp"
#include "Common.hpp"
#include "Log.hpp"
#include "Connection.hpp"

using func_t = std::function<std::string(std::string&)>;  // 注意这里是引用，因为会拿走缓冲区的数据，要实时更新

// 专门用来处理普通文件描述符的，对应着Listenner
class IOservice : public Connection  // 天然就有父类Connection
{
public:
    static const int maxsize = 1024;

    IOservice(int sockfd)
    {
        SetSockfd(sockfd);  // 将sockfd设置进去
        SetEvents(EPOLLIN | EPOLLET);  // 设置关心的事件
    }

    // 设置回调函数执行方法
    virtual void Sender() override
    {
        // 直接写，干满了再托管给epoll关心处理
        while(true)
        {
            ssize_t n = send(Sockfd(), GetOutBuffer().c_str(), GetOutBuffer().size(), 0);  // 默认是非阻塞，之前已经设置过了
            // 返回值是实际发送的字节数目
            if(n > 0)
            {
                // 发送成功
                DiscardOutBuffer(n);  // 移除前 n 个字节的数据
            }
            else if(n == 0)
            {
                // 发送缓冲区的数据发送完毕了
                break;
            }
            else
            {
                // 发送错误了，还是跟 写 一样
                if(errno == EAGAIN || errno == EWOULDBLOCK)
                {
                    // 缓冲区写满了，下次再来
                    break;
                }
                else if(errno == EINTR)
                {
                    continue;
                }
                else
                {
                    // 转化给异常处理
                    Excepter();
                    return;
                }
            }
        }
        // 走到这里，只有两种情况：1. 接受缓冲区写满了，但是发送缓冲区还没清空  2. 发送缓冲区数据发完了
        // 如果是第一种情况，那么我们还需要再发送一次，所以要交给epoll管理，让它托管一下，下一次写事件就绪再发送一次
        if(!_outbuffer.empty())
        {
            // 发送缓冲区还没清空，还没发送完成
            _owner->EnableReadWrite(Sockfd(), true, true);  // 关心写事件，下一次就绪了再发
        }
        else
        {
            // 发送缓冲区清空了，不需要再写了
            _owner->EnableReadWrite(Sockfd(), true, false);  // 关闭写事件，写事件需要按需设置
        }
    }

    virtual void Recver() override
    {
        // LOG(LogLevel::DEBUG) << "hahaha,i am login";
        // 1. 读取所有数据
        while(true)  // 为什么要循环读？因为是ET模式，必须全部读完
        {
            char buffer[maxsize];
            ssize_t n = ::recv(Sockfd(), buffer, sizeof(buffer)-1, 0);  // 默认就是非阻塞，之前已经设置过了
            if(n > 0)
            {
                // 读取成功
                buffer[n] = 0; 
                Append(buffer); 
            }
            else if(n == 0)
            {
                // 对端关闭连接了,转化为异常处理
                Excepter();
                return;
            }
            else
            {
                // 分三种情况：底层缓冲区读取完毕返回EAGIN或者EWOULDBLOCK，还有就是被信号中断了，最后就是真的出错了
                if(errno == EAGAIN || errno == EWOULDBLOCK)
                {
                    break;
                }
                else if(errno == EINTR)
                {
                    continue;
                }
                else
                {
                    // 转化给异常处理
                    Excepter();
                    return;
                }
            }
        }
        // 走到这里，我能保证本轮的数据已经读取完毕了
        std::cout << "outbuffer: \n" << GetInbuffer() << std::endl;
        // 我们不能保证读到的报文是完整的，所以需要用协议来保证！(协议我们使用的是网络版本计算器的协议，只是用来保证读取的报文完整性而已)
        std::string result;
        if(_onmessage)  // 如果设置了回调函数，才处理
            result = _onmessage(GetInbuffer());
        // 此时的result代表应答信息，已经进行了处理
        // 添加应答信息到发送缓冲区
        AppendToOut(result);
        // 接下来处理的就是如何处理“写”的问题，也就是outbuffer如何发送到对方的问题
        // 多路转接对写事件的处理：与读不同，读 是先托管给epoll，再读；而写是直接写！如果发送缓冲区满了，再托管给epoll
        if(!_outbuffer.empty()) 
        {
            // 方案一：直接写，推荐做法 
            Sender();
            // 方案二：使write事件关心起来，也就是writeable为true
            // GetOwner()->EnableReadWrite(Sockfd(), true, true);
        }
    }

    virtual void Excepter() override
    {
        // 由于我们在 Dispatcher 事件派发的时候，将异常事件转化成读+写事件，所以在IO读取的时候，所以任何异常，都将会转化在这个函数中进行处理
        LOG(LogLevel::INFO) << "客户端连接可能结束，进行异常处理：" << Sockfd();
        GetOwner()->DelConnection(Sockfd());
    }

    // 注册回调函数
    void RigesterOnMessage(func_t onmessage)
    {
        _onmessage = onmessage;
    }

    ~IOservice(){}

private:
    func_t _onmessage;  // 处理消息的回调函数，用来进行业务处理
};